using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;

namespace NNFramework.NeuroLibrary
{
    [Serializable]
    public class clsNeuron<TInput, TOutput> : clsBase
        where TInput: struct
        where TOutput : struct
    {

        #region Delegates Section

        public delegate TOutput ActivationFunctionDelegate(double pdblFx);

        #endregion

        #region Members Section

        //private List<clsNeuron<TInput, TOutput>> mcngReferences; //maybe will disappear
        private Collection<Guid?> muidReferences;       
        private Collection<double> mdblWeights;
        private Collection<TInput> mgenInputs;
        private ActivationFunctionDelegate mafdActivateFunction;
        private NeuronState mnstState;
        private Guid mguiID;        
        private object mnncNetwork;

        #endregion

        #region Properties Section

        public Guid GUID
        {
            get 
            { 
                return mguiID; 
            }
            set 
            { 
                mguiID = value; 
            }
        }

        public Collection<Guid?> ReferencedNeurons
        {
            get 
            { 
                return muidReferences; 
            }            
        }

        public int DendritesCount
        {
            get
            {
                return mgenInputs.Count;
            }
        }

        public TOutput Axon
        {
            get
            {
                return mafdActivateFunction(CalculateValue());
            }
        }

        public NeuronState State
        {
            get
            {
                return mnstState;
            }
        }

        public Collection<TInput> Dendrites
        {
            get
            {
                return mgenInputs;
            }
        }

        public Collection<double> Weights
        {
            get
            {
                return mdblWeights;
            }
        }

        /// <summary>
        /// Returns the neuron's activation function name
        /// </summary>
        public String ActivationFunction
        {
            get
            {
                return mafdActivateFunction.Method.Name;
            }
        }

        #endregion
       

        #region Events Section

        public event EventHandler<DendriteAddedEventArgs> DendriteAdded;
        
        #endregion

        #region Constructors Region

        /// <summary>
        /// Default Constructor
        /// </summary>
        public clsNeuron()
        {
           // mcngReferences = new List<clsNeuron<TInput, TOutput>>(); //will Disappear
            muidReferences = new Collection<Guid?>();
            mdblWeights = new Collection<double>();
            mgenInputs = new Collection<TInput>();
            //anonymous function            
            mafdActivateFunction = delegate(double pdblFx) 
            {
                object toupValue;
                toupValue = pdblFx >= 1 ? 1 : 0;
                return (TOutput)toupValue;
            };
            mnstState = NeuronState.Created;
            mguiID = Guid.NewGuid();
            mnncNetwork = null;
        }

        /// <summary>
        /// Creates a Neuron setting the Activation Function
        /// </summary>
        /// <param name="pafdFunction">The Activation Function Delegate</param>
        public clsNeuron(ActivationFunctionDelegate pafdFunction)
        {
            //mcngReferences = new List<clsNeuron<TInput, TOutput>>();
            muidReferences = new Collection<Guid?>();
            mdblWeights = new Collection<double>();
            mgenInputs = new Collection<TInput>();
            mafdActivateFunction = pafdFunction;
            mnstState = NeuronState.Created;
            mguiID = Guid.NewGuid();
            mnncNetwork = null;
        }

        /// <summary>
        /// Creates a neuron setting the Activation Function and the parent object
        /// </summary>
        /// <param name="pafdFunction">The Activation Function</param>
        /// <param name="pntkParent">The parent Object</param>
        public clsNeuron(ActivationFunctionDelegate pafdFunction, object pntkParent)
        {
           // mcngReferences = new List<clsNeuron<TInput, TOutput>>();
            muidReferences = new Collection<Guid?>();
            mdblWeights = new Collection<double>();
            mgenInputs = new Collection<TInput>();
            mafdActivateFunction = pafdFunction;
            mnstState = NeuronState.Created;
            mguiID = Guid.NewGuid();
            mnncNetwork = pntkParent;
        }

        public clsNeuron(Collection<double> pdblWeights, Collection<TInput> pgenInputs, ActivationFunctionDelegate pafdFunction)
        {
           // mcngReferences = pcngReferences;
            muidReferences = new Collection<Guid?>();
            mdblWeights = pdblWeights;
            mgenInputs = pgenInputs;
            mafdActivateFunction = pafdFunction;
            mnstState = NeuronState.Created;
            mguiID = Guid.NewGuid();
            mnncNetwork = null;
        }

        public clsNeuron(Collection<double> pdblWeights, Collection<TInput> pgenInputs, ActivationFunctionDelegate pafdFunction, Guid pgidID)
        {
           // mcngReferences = pcngReferences;
            muidReferences = new Collection<Guid?>();
            mdblWeights = pdblWeights;
            mgenInputs = pgenInputs;
            mafdActivateFunction = pafdFunction;
            mnstState = NeuronState.Created;
            mguiID = pgidID;
            mnncNetwork = null;
        }

        public clsNeuron(Collection<double> pdblWeights, Collection<TInput> pgenInputs, ActivationFunctionDelegate pafdFunction, Guid pgidID, object pntkParent)
        {
           // mcngReferences = pcngReferences;
            muidReferences = new Collection<Guid?>();
            mdblWeights = pdblWeights;
            mgenInputs = pgenInputs;
            mafdActivateFunction = pafdFunction;
            mnstState = NeuronState.Created;
            mguiID = pgidID;
            mnncNetwork = pntkParent;
        }

        public clsNeuron(Collection<double> pdblWeights, Collection<TInput> pgenInputs, ActivationFunctionDelegate pafdFunction, Guid pgidID, object pntkParent, Collection<Guid?> puidReference)
        {
           // mcngReferences = pcngReferences;
            muidReferences = puidReference;
            mdblWeights = pdblWeights;
            mgenInputs = pgenInputs;
            mafdActivateFunction = pafdFunction;
            mnstState = NeuronState.Created;
            mguiID = pgidID;
            mnncNetwork = pntkParent;
        }

        #endregion

        #region Protected Methods Section

        protected virtual void OnDendriteAdded(DendriteAddedEventArgs e)
        {
            if (DendriteAdded != null)
                DendriteAdded(this, e);
        }


        #endregion

        #region Private Methods Section

        private double CalculateValue()
        {
            //maybe reflecting this could be better, or the boxing could hit the performance?
            double dblResult = 0;            
            for (int i = 0; i < mgenInputs.Count; i++)
            {                               
                dblResult += Convert.ToDouble(mgenInputs[i]) * mdblWeights[i];                
            }
            return dblResult;
        }

        //deprecated, but very good performance
        //private void GetInputs2()
        //{            
        //    for (int i = 0; i < mcngReferences.Count; i++)
        //    {
        //        if (mcngReferences[i] != null)
        //        {
        //            //this type of cast isn't too much efective, it boxes the value, the cast it, perfomance hitted                    
        //            mgenInputs[i] = (TInput)Convert.ChangeType(mcngReferences[i].Axon, typeof(TInput));
        //        }                
        //    }
        //}

        //deprecated too, the neuron must be searched in the networks instead only the layer, maybe
        //could the architecture could helps to the method
        //private void GetInputs3()
        //{
        //    //this methods replaces the lastone because the GUID added for serializing
        //    Guid? uidToSearch = null;
        //    clsNeuron<TInput, TOutput> cnnFound = null;
        //    Predicate<clsNeuron<TInput, TOutput>> ToMatch = delegate(clsNeuron<TInput, TOutput> NeuronToCompare)
        //    {
        //        bool blnResult = false;
        //        if (uidToSearch != null)
        //        {
        //            if (NeuronToCompare.GUID == uidToSearch)
        //            {
        //                blnResult = true;
        //            } 
        //        }
        //        return blnResult;
        //    };
        //    for (int i = 0; i < muidReferences.Count; i++)
        //    {
        //        uidToSearch = muidReferences[i];
        //        cnnFound = mlyrParent.Neurons.Find(ToMatch);
        //        if (cnnFound != null)
        //        {
        //            mgenInputs[i] = (TInput)Convert.ChangeType(cnnFound.Axon, typeof(TInput));                    
        //        }
        //    }     
            
        //}

        private void GetInputs()
        {
            //this methods replaces the last 2 because the GUID added for serializing and the network search
            //maybe this is really heavy method
            //The null verification is to help to play with only one neuron
            if(mnncNetwork != null)
            {
                clsNeuron<TInput, TOutput> cnnFound = null;
                if (mnncNetwork.GetType().IsGenericType)
                {
                    //reduce the time to learn or process?? get out the verifications, but can get serious untraceble errors too...
                    Type[] typToNetwork = mnncNetwork.GetType().GetGenericArguments();
                    if ((typToNetwork.Length < 4) && (typToNetwork.Length > 0))
                    {
                        for (int i = 0; i < muidReferences.Count; i++)
                        {
                            //mgenInputs[i] = (TInput)Convert.ChangeType(((clsNeuron<TInput, TOutput>)mnncNetwork.GetType().GetMethod("GetNeuronFromGUID").Invoke(mnncNetwork, BindingFlags.ExactBinding, null, new object[1] { muidReferences[i] }, null)).Axon, typeof(TInput));                 
                            cnnFound = (clsNeuron<TInput, TOutput>)mnncNetwork.GetType().GetMethod("GetNeuronFromGUID").Invoke(mnncNetwork, BindingFlags.ExactBinding, null, new object[] { muidReferences[i] }, null);
                            if (cnnFound != null)
                            {                            
                                mgenInputs[i] = (TInput)Convert.ChangeType(cnnFound.Axon, typeof(TInput));                            
                            }
                        }                   
                        
                    }
                    else
                    {
                        throw new ArgumentException("The object don't have enought Generic Parameters", "mnncNetwork");                
                    }

                }
                else
                {
                    throw new ArgumentException("The object passed as Network isn't a Generic Type", "mnncNetwork");
                }
            }
        }


        #endregion

        #region Public Methods Section

        public void AddDendrite(clsNeuron<TInput, TOutput> pcnnInput, double pdblWeight, TInput pgenInput)
        {            
            muidReferences.Add((pcnnInput != null) ? pcnnInput.GUID : (Guid?)null);
            mdblWeights.Add(pdblWeight);
            mgenInputs.Add(pgenInput);
            mnstState = NeuronState.DirtyValue;
            EvaluateMe();
            mnstState = NeuronState.Evaluated;
            OnDendriteAdded(new DendriteAddedEventArgs());
        }

        public void AddDendriteReflected(clsNeuron<TInput, TOutput> pcnnInput, double pdblWeight, TInput pgenInput)
        {           
            muidReferences.Add((pcnnInput != null) ? pcnnInput.GUID : (Guid?)null);
            mdblWeights.Add(pdblWeight);
            mgenInputs.Add(pgenInput);
            mnstState = NeuronState.DirtyValue;
            EvaluateMe();
            mnstState = NeuronState.Evaluated;
            OnDendriteAdded(new DendriteAddedEventArgs());
        }

        public void AddDendrite(clsNeuron<TInput, TOutput> pcnnInput, TInput ptipValue)
        {
            Random rndWeight = new Random();            
            AddDendrite(pcnnInput, rndWeight.NextDouble(), ptipValue);            
        }

        public void AddDendrite(TInput ptipValue)
        {
            Random rndWeight = new Random();
            AddDendrite(null, rndWeight.NextDouble(), ptipValue);
        }

        public void AddDendrite(TInput ptipValue, double pdblWeight)
        {            
            AddDendrite(null, pdblWeight, ptipValue);
        }

        public void AddDendrite(clsDendrite<TInput> poddDendrite)
        {   
            AddDendrite(null, poddDendrite.DendriteWeight, poddDendrite.DendriteValue);
        }

        public void AddDendrite(clsNeuron<TInput, TOutput> pcnnInput, clsDendrite<TInput> poddDendrite)
        {

            AddDendrite(pcnnInput, poddDendrite.DendriteWeight, poddDendrite.DendriteValue);
        }

        /// <summary>
        /// Evaluates the Weights And Values based on the connected Neurons
        /// </summary>
        public void EvaluateMe()
        {
            GetInputs();
            mnstState = NeuronState.Evaluated;
        }       
        
        /// <summary>
        /// Returns a Dictionary with the Values and the Weights of this Neuron's Dendrites
        /// </summary>
        /// <returns>A Dictionary with the Weights and Values of the Dendrites</returns>
        public Dictionary<int, double> GetDendritesWeigths()
        {
            Dictionary<int, double> dicResult = new Dictionary<int, double>();
            for (int i = 0; i < mgenInputs.Count; i++)
            {
                dicResult.Add(i, mdblWeights[i]);
            }
            return dicResult;
        }

        /// <summary>
        /// Returns a Dictionary with the Values and the Weights of this Neuron's Dendrites
        /// </summary>
        /// <returns>A Dictionary with the Weights and Values of the Dendrites</returns>
        public Dictionary<int, TInput> GetDendritesValues()
        {
            Dictionary<int, TInput> dicResult = new Dictionary<int, TInput>();
            for (int i = 0; i < mgenInputs.Count; i++)
            {
                dicResult.Add(i, mgenInputs[i]);
            }
            return dicResult;
        }

        /// <summary>
        /// Gets a list with Dendrites objects for this Neuron
        /// </summary>
        /// <returns>A List of Dendrites for this Neuron</returns>
        public List<clsDendrite<TInput>> GetDendrites()
        {
            //It would be good that there exist a List of Dendrites as members, but the original design doesn't support it
            //So I must add the class and this support to get a better DataBinding support.
            List<clsDendrite<TInput>> lstResult = new List<clsDendrite<TInput>>();

            for (int i = 0; i < mgenInputs.Count; i++)
            {
                lstResult.Add(new clsDendrite<TInput>(mgenInputs[i], mdblWeights[i]));
            }

            return lstResult;
        }

        #endregion

        #region Public Static Methods Section

        public static clsNeuron<TInput, TOutput> LoadNeuronFromXml(string pstrFileName)
        {
            XmlSerializer xslSerializer = new XmlSerializer(typeof(clsNeuron<TInput, TOutput>));
            FileStream flsFile = new FileStream(pstrFileName, FileMode.Open);
            return (clsNeuron<TInput, TOutput>)xslSerializer.Deserialize(flsFile);
        }

        #endregion
    }    
}
